#include "TetrisBoard.h"
#include "TetrisPiece.h"

using namespace Ogre;

template<> cTetrisBoard* Singleton<cTetrisBoard>::msSingleton = NULL;

cTetrisBoard::cTetrisBoard()
:	m_LeftPlane	(Vector3(0.f, 0.f, -1.f), Vector3(0.f, 0.f, 1.f), Vector3(0.f, 1.f, 0.f)) 
,	m_RightPlane(Vector3(MOD_GRID_LINES * MOD_CUBE_SIZE, 0.f, -1.f),
				 Vector3(MOD_GRID_LINES * MOD_CUBE_SIZE, 0.f, 1.f), 
				 Vector3(MOD_GRID_LINES * MOD_CUBE_SIZE, 1.f, 0.f))
,	m_DownPlane	(Vector3(0.f, 0.f, 0.f), Vector3(1.f, 0.f, 1.f), Vector3(0.f, 0.f, -1.f))
,	m_aabb(Vector3::NEGATIVE_UNIT_X, Vector3::UNIT_X)
,	m_vMin(Vector3::ZERO)
,	m_vMax(Vector3::ZERO)
{
	Root::getSingleton().addFrameListener(this);
}

cTetrisBoard::~cTetrisBoard()
{
	Root::getSingleton().removeFrameListener(this);
}

bool cTetrisBoard::frameStarted(const Ogre::FrameEvent& evt)
{
#ifdef _DEBUG
	if (g_pDebugDrawer)
	{
		g_pDebugDrawer->drawCuboid(m_aabb.getAllCorners(), ColourValue::Red, false);
		//g_pDebugDrawer->drawSphere(m_vMin, 1.f, ColourValue::Red, true);
		//g_pDebugDrawer->drawSphere(m_vMax, 1.f, ColourValue::Red, true);				
		g_pDebugDrawer->build();
	}
#endif //_DEBUG
	return true;	
}

bool cTetrisBoard::frameEnded (const Ogre::FrameEvent& evt)
{
#ifdef _DEBUG
	if (g_pDebugDrawer) 
		g_pDebugDrawer->clear();
#endif //_DEBUG
	return true;	
}

DWORD cTetrisBoard::GetCollissionType(cTetrisPiece& piece, const Ogre::Vector3& vDist)
{			
	if (!piece.GetParentNode())
		return eFlagNone;
	AxisAlignedBox aabb = piece.GetParentNode()->_getWorldAABB();
	aabb.setMaximum(aabb.getMaximum() + vDist);
	aabb.setMinimum(aabb.getMinimum() + vDist);

	DWORD dwCollFlag = 0;
	if (aabb.intersects(m_LeftPlane))
		dwCollFlag |= eFlagWallLeft;
	if (aabb.intersects(m_RightPlane))
		dwCollFlag |= eFlagWallRight;
	if (aabb.intersects(m_DownPlane) || m_DownPlane.getSide(aabb) == Plane::NEGATIVE_SIDE)
		dwCollFlag |= eFlagWallDown;

	dwCollFlag |= CubeCollision(piece, vDist);
	
	if (dwCollFlag == 0)
		return eFlagNone;
	return dwCollFlag;
}

DWORD cTetrisBoard::GetCollissionType(const Ogre::AxisAlignedBox& box, const Ogre::Vector3& vDist)
{
	AxisAlignedBox aabb(box); 

	aabb.setMaximum(aabb.getMaximum() + vDist);
	aabb.setMinimum(aabb.getMinimum() + vDist);

	if (aabb.intersects(m_LeftPlane))
		return eFlagWallLeft;
	if (aabb.intersects(m_RightPlane))
		return eFlagWallRight;
	if (aabb.intersects(m_DownPlane) || m_DownPlane.getSide(aabb) == Plane::NEGATIVE_SIDE)
		return eFlagWallDown;

	if (IsCollision(aabb))
		return eFlagCube;

	return eFlagNone;	
}

bool cTetrisBoard::CanRotatePiece(cTetrisPiece& piece, const Ogre::Vector3& vAxis, const Ogre::Radian& radian)
{
	if (!piece.GetParentNode())
		return false;
	AxisAlignedBox aabb		= piece.GetParentNode()->_getWorldAABB();
	if (aabb.isInfinite() || aabb.isNull())
		return false;

	aabb.setMinimum(piece.GetParentNode()->convertWorldToLocalPosition(aabb.getMinimum()));
	aabb.setMaximum(piece.GetParentNode()->convertWorldToLocalPosition(aabb.getMaximum()));
	
	Matrix3 mat3Rot;
	mat3Rot.FromEulerAnglesXYZ(Radian(0.f), Radian(0.f), radian);
	aabb.transform(mat3Rot);
	
	Vector3 vMin = piece.GetParentNode()->convertLocalToWorldPosition(aabb.getMinimum());
	Vector3 vMax = piece.GetParentNode()->convertLocalToWorldPosition(aabb.getMaximum());
	
	float temp;
	if (vMin.x > vMax.x && vMin.y > vMax.y)
	{
		temp	= vMin.x;
		vMin.x	= vMax.x;
		vMax.x	= temp;

		temp	= vMin.y;
		vMin.y	= vMax.y;
		vMax.y	= temp;
	}
	else if (vMin.x > vMax.x)
	{
		temp	= vMin.x;
		vMin.x	= vMax.x;
		vMax.x	= temp;	
	}
	else if (vMin.y > vMax.y)
	{
		temp	= vMin.y;
		vMin.y	= vMax.y;
		vMax.y	= temp;
	}

	aabb.setMinimum(vMin);
	aabb.setMaximum(vMax);

#ifdef _DEBUG
	m_aabb.setMinimum(aabb.getMinimum());
	m_aabb.setMaximum(aabb.getMaximum());
	//m_vMin = aabb.getMinimum();
	//m_vMax = aabb.getMaximum();
	m_vMin = vMin;
	m_vMax = vMax;
#endif // _DEBUG

	if (aabb.intersects(m_LeftPlane))
		return false;
	if (aabb.intersects(m_RightPlane))
		return false;
	if (aabb.intersects(m_DownPlane))
		return false;
	
	if (IsCollision(aabb))
		return false;

	return true;
}

bool cTetrisBoard::IsCollision(cTetrisPiece& piece, const Ogre::Vector3& vDist)
{
	AxisAlignedBox aabb;	
	SceneNode* pNodeCube = NULL;
	for (int i = 0; i < MOD_GRID_LINES; i++)
	{
		for (int j = 0; j < MOD_GRID_COLUMN; j++)
		{
			if (m_Grid[i][j].pNodeCube)
			{
				Node::ChildNodeIterator	cubeIter = piece.GetParentNode()->getChildIterator();
				while(cubeIter.hasMoreElements()) 
				{
					pNodeCube	= (SceneNode*)cubeIter.current()->second;
					aabb		= pNodeCube->_getWorldAABB();
					aabb.setMaximum(aabb.getMaximum() + vDist);
					aabb.setMinimum(aabb.getMinimum() + vDist);
					// Keep a small distance between cubes
					//aabb.setMinimum(aabb.getMinimum() - Vector3(0.f, 0.5f, 0.f));
					if (m_Grid[i][j].pNodeCube->_getWorldAABB().intersects(aabb))
						return true;
					cubeIter.getNext();
				}						
			}
		}
	}
	return false;
}

DWORD cTetrisBoard::CubeCollision(cTetrisPiece& piece, const Ogre::Vector3& vDist)
{
	DWORD dwCollission = 0;
	AxisAlignedBox A, B;	
	SceneNode*	pNodeCube = NULL;
	Vector3	vOffset(Vector3::ZERO);
	float fOffset = 0.f;
	int	nOffset = 0;
	int XValue = 0;
	int YValue = 0;
	tRect	rectA, rectB;
	for (int i = 0; i < MOD_GRID_LINES; i++)
	{
		for (int j = 0; j < MOD_GRID_COLUMN; j++)
		{
			if (m_Grid[i][j].pNodeCube)
			{
				Node::ChildNodeIterator	cubeIter = piece.GetParentNode()->getChildIterator();
				while(cubeIter.hasMoreElements()) 
				{
					pNodeCube	= (SceneNode*)cubeIter.current()->second;
					A			= pNodeCube->_getWorldAABB();
					A.setMaximum(A.getMaximum() + vDist);
					A.setMinimum(A.getMinimum() + vDist);
			
					if (m_Grid[i][j].pNodeCube->_getWorldAABB().intersects(A))
					{
						if (vDist.x < 0.f)
							dwCollission |= eFlagCubeLeft;
						else if (vDist.x > 0.f)				
							dwCollission |= eFlagCubeRight;	
						else if (vDist.y < 0.f)
						{
							dwCollission |= eFlagCubeDown;
							/*							
							B = m_Grid[i][j].pNodeCube->_getWorldAABB();
							rectA.x1 = 	A.getMinimum().x;
							rectA.y1 = 	A.getMaximum().y;
							rectA.x2 = 	A.getMaximum().x;
							rectA.y2 = 	A.getMinimum().y;

							rectB.x1 = 	B.getMinimum().x;
							rectB.y1 = 	B.getMaximum().y;
							rectB.x2 = 	B.getMaximum().x;
							rectB.y2 = 	B.getMinimum().y;

							bool bLeft  = false;
							bool bRight = false;
							if (rectA.y1 > rectB.y1)
							{
								if (rectA.x2 < rectB.x2 && rectA.x2 > rectB.x1 && rectA.y2 < rectB.y1)
								{
									fOffset = rectB.x2 - rectA.x2;
									nOffset = (int)fOffset;
									bLeft = true;
								}
								if (rectA.x1 < rectB.x2 && rectA.x1 > rectB.x1 && rectA.y2 < rectB.y1)
								{
									fOffset = rectB.x1 - rectA.x1;
									nOffset = (int)fOffset;
									bRight = true;
								}
								if (abs(fOffset) >= 2.5f)
								{
									XValue	= (int)(A.getCenter().x / MOD_CUBE_SIZE);
									YValue	= (int)(A.getCenter().y / MOD_CUBE_SIZE);	
									if (m_Grid[YValue][XValue].pNodeCube != NULL)
										return dwCollission |= eFlagCubeDown;
									else
										return eFlagNone;
								}
							}
							*/
						}						
					}
					cubeIter.getNext();
				}						
			}
		}
	}
	return dwCollission;	
}

bool cTetrisBoard::IsCollision(const Ogre::AxisAlignedBox& aabb)
{
	int XValue = 0;
	int YValue = 0;
	for (int i = 0; i < MOD_GRID_LINES; i++)
	{
		for (int j = 0; j < MOD_GRID_COLUMN; j++)
		{
			if (m_Grid[i][j].pNodeCube && m_Grid[i][j].pNodeCube->_getWorldAABB().intersects(aabb))
				return true;
		}
	}
	return false;
}

bool cTetrisBoard::IsEmpty()
{
	for (int i = 0; i < MOD_GRID_COLUMN; i++)
	{
		if (m_Grid[0][i].pNodeCube)	
			return false;
	}
	return true;
}

bool cTetrisBoard::BreakPiece(cTetrisPiece& piece, const Vector3& vLastDir)
{
	if (!piece.GetParentNode())
		return false;

	PrintDebugGrid();

	Node::ChildNodeIterator iter = piece.GetParentNode()->getChildIterator();
	String szName;
	AxisAlignedBox aabb;	
	SceneNode* pNodeCube = NULL;

	float   fOffset = 0.f;
	Vector3	vOffset(Vector3::ZERO);

	int XValue = 0;
	int YValue = 0;
	char szMsg[64];
	int nSign = 0;
	while(iter.hasMoreElements()) 
	{
		szName		= iter.current()->second->getName();
		pNodeCube	= (SceneNode*)iter.current()->second;
		aabb		= pNodeCube->_getWorldAABB();

		XValue	= (int)(aabb.getCenter().x / MOD_CUBE_SIZE);
		YValue	= (int)(aabb.getCenter().y / MOD_CUBE_SIZE);
		
		sprintf(szMsg, "->>>> line = %d column = %d is NULL = %s", YValue, XValue, (m_Grid[YValue][XValue].pNodeCube?"1":"0"));
		AppFramework::getSingletonPtr()->m_pLog->logMessage(szMsg);									
		if (m_Grid[YValue][XValue].pNodeCube != NULL)
		{
			nSign = vLastDir.x < 0.f ? 1 : -1;
		}

	    assert( XValue >= 0 && XValue < MOD_GRID_LINES && YValue >= 0  && "Out of m_Grid bounds");	
		if (YValue >= MOD_GRID_COLUMN)
			return false;

		if (aabb.getMinimum().x > (XValue * MOD_CUBE_SIZE))
		{
			fOffset = aabb.getMinimum().x - (XValue * MOD_CUBE_SIZE);
			vOffset = Vector3(pNodeCube->_getDerivedPosition().x - fOffset, pNodeCube->_getDerivedPosition().y, pNodeCube->_getDerivedPosition().z);
			pNodeCube->_setDerivedPosition(vOffset);
		}
		else if (aabb.getMinimum().x < (XValue * MOD_CUBE_SIZE))
		{
			fOffset = (XValue * MOD_CUBE_SIZE) - aabb.getMinimum().x;
			vOffset = Vector3(pNodeCube->_getDerivedPosition().x + fOffset, pNodeCube->_getDerivedPosition().y, pNodeCube->_getDerivedPosition().z);
			pNodeCube->_setDerivedPosition(vOffset);
		}

		if (aabb.getMinimum().y > (YValue * MOD_CUBE_SIZE))
		{
			fOffset = aabb.getMinimum().y - (YValue * MOD_CUBE_SIZE);
			vOffset = Vector3(pNodeCube->_getDerivedPosition().x, pNodeCube->_getDerivedPosition().y - fOffset, pNodeCube->_getDerivedPosition().z);
			pNodeCube->_setDerivedPosition(vOffset);			
		}
		else if (aabb.getMinimum().y < (YValue * MOD_CUBE_SIZE))
		{
			fOffset = (YValue * MOD_CUBE_SIZE) - aabb.getMinimum().y;
			vOffset = Vector3(pNodeCube->_getDerivedPosition().x, pNodeCube->_getDerivedPosition().y + fOffset, pNodeCube->_getDerivedPosition().z);
			pNodeCube->_setDerivedPosition(vOffset);					
		}

		if (nSign == 0)
		{
			// From Cartesian to Grid : x -> column, y -> line
			m_Grid[YValue][XValue].pNodeCube = pNodeCube;
		}
		iter.getNext();
	}
	PrintDebugGrid();
	return true;
}

void cTetrisBoard::AdjustPiece(cTetrisPiece& piece, const Vector3& vAdjustDir)
{
	float fAdjustDist = GetAdjustValue(piece.GetParentNode()->_getWorldAABB(), vAdjustDir);

	DWORD dwFlag = GetCollissionType(piece, Vector3(vAdjustDir.x * fAdjustDist, vAdjustDir.y, vAdjustDir.z)); 
	if ((dwFlag & eFlagWallLeft) == false && (dwFlag & eFlagWallRight) == false && 
		(dwFlag & eFlagCubeLeft) == false && (dwFlag & eFlagCubeRight) == false)
	{
		piece.Update(0.f, Vector3::ZERO, 0.f, Vector3(vAdjustDir.x * fAdjustDist, vAdjustDir.y, vAdjustDir.z));
	}
}

float cTetrisBoard::GetAdjustValue(const Ogre::AxisAlignedBox& aabb, const Vector3& vAdjustDir)
{
	float fPos	  = aabb.getMinimum().x;
	float fValue  = fPos / MOD_CUBE_SIZE;
	int	  nValue  = int(fValue + Matrix3::EPSILON);
	float fDiff   = fValue - (float)nValue; 

	float fAdjustDist = fDiff * MOD_CUBE_SIZE - 0.01f;
	if (vAdjustDir.x > 0.f && fDiff > 0.01f)
		fAdjustDist = (1.f - fDiff) * MOD_CUBE_SIZE;
	
	return fAdjustDist;
}

void cTetrisBoard::PrintDebugGrid()
{
	String szLine;
	AppFramework::getSingletonPtr()->m_pLog->logMessage("============================== \n");						
	for (int i = MOD_GRID_LINES - 1; i >= 0; i--)
	{
		for (int j = 0; j < MOD_GRID_COLUMN; j++)
			szLine += m_Grid[i][j].pNodeCube?" 1":" 0";
		AppFramework::getSingletonPtr()->m_pLog->logMessage(szLine);						
		szLine .clear();
	}
}

void cTetrisBoard::ClearAll(Ogre::SceneManager* pSceneMgr)			
{
	for (int i = 0; i < MOD_GRID_LINES; i++)
	{
		for (int j = 0; j < MOD_GRID_COLUMN; j++)
		{
			if (m_Grid[i][j].pNodeCube)
			{
				pSceneMgr->destroySceneNode(m_Grid[i][j].pNodeCube->getName());
				m_Grid[i][j].pNodeCube = NULL;
			}
		}
	}
}

int cTetrisBoard::DeleteLines(SceneManager* pSceneMgr)
{
	int line = CheckForLines();
	if (line == -1)
		return 0;

	int countLines = 0;
	std::list <int> removedLines;
	while (line != -1)
	{
		countLines++;
		removedLines.push_back(line);
		for (int j = 0; j < MOD_GRID_COLUMN; j++)
		{
			if (m_Grid[line][j].pNodeCube)
			{
				pSceneMgr->destroySceneNode(m_Grid[line][j].pNodeCube->getName());
				m_Grid[line][j].pNodeCube = NULL;
			}
		}
		line = CheckForLines();
	}
	//PrintDebugGrid();

	Vector3 vOffset;
	std::list <int>::iterator iter;  
	int count = 0;
	for (iter = removedLines.begin(); iter != removedLines.end(); iter++)
	{
		for (int k = (*iter) - count; k < MOD_GRID_LINES; k++)
		{
			for (int j = 0; j < MOD_GRID_COLUMN; j++)
			{					
				if (k + 1 < MOD_GRID_LINES)
					m_Grid[k][j].pNodeCube = m_Grid[k + 1][j].pNodeCube;
				else
					m_Grid[k][j].pNodeCube = NULL;

				if (m_Grid[k][j].pNodeCube)
				{
					vOffset = Vector3(	m_Grid[k][j].pNodeCube->_getDerivedPosition().x, 
										m_Grid[k][j].pNodeCube->_getDerivedPosition().y - MOD_CUBE_SIZE, 
										m_Grid[k][j].pNodeCube->_getDerivedPosition().z);
					m_Grid[k][j].pNodeCube->_setDerivedPosition(vOffset);
				}
			}
		}
		count++;		
		//PrintDebugGrid();		
	}
	
	return countLines;
}

int cTetrisBoard::CheckForLines()
{
	for (int i = 0; i < MOD_GRID_LINES; i++)
	{
		bool bFound = true;
		for (int j = 0; j < MOD_GRID_COLUMN; j++)
		{
			if (m_Grid[i][j].pNodeCube == NULL)
				bFound = false;
		}
		if (bFound)
			return i;
	}

	return -1;
}
